package com.koushikdutta.async.http;

import android.net.Uri;
import android.util.Base64;
import com.koushikdutta.async.AsyncSSLSocket;
import com.koushikdutta.async.AsyncServer;
import com.koushikdutta.async.AsyncSocket;
import com.koushikdutta.async.ByteBufferList;
import com.koushikdutta.async.DataEmitter;
import com.koushikdutta.async.FilteredDataEmitter;
import com.koushikdutta.async.Util;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.callback.WritableCallback;
import com.koushikdutta.async.future.Cancellable;
import com.koushikdutta.async.future.SimpleCancellable;
import com.koushikdutta.async.http.AsyncHttpClientMiddleware.GetSocketData;
import com.koushikdutta.async.http.AsyncHttpClientMiddleware.OnBodyData;
import com.koushikdutta.async.http.AsyncHttpClientMiddleware.OnRequestCompleteData;
import com.koushikdutta.async.http.libcore.RawHeaders;
import com.koushikdutta.async.http.libcore.ResponseHeaders;
import com.koushikdutta.async.http.libcore.ResponseSource;
import com.koushikdutta.async.http.libcore.StrictLineReader;
import com.koushikdutta.async.util.Allocator;
import com.koushikdutta.async.util.Charsets;
import com.koushikdutta.async.util.FileCache;
import com.koushikdutta.async.util.StreamUtility;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.CacheResponse;
import java.nio.ByteBuffer;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class ResponseCacheMiddleware extends SimpleMiddleware {
    private FileCache cache;
    private int cacheHitCount;
    private int cacheStoreCount;
    private boolean caching = true;
    private int conditionalCacheHitCount;
    private int networkCount;
    private AsyncServer server;
    private int writeAbortCount;
    private int writeSuccessCount;

    private static class BodyCacher extends FilteredDataEmitter {
        ByteBufferList cached;
        EntryEditor editor;

        private BodyCacher() {
        }

        protected void report(Exception e) {
            super.report(e);
            if (e != null) {
                abort();
            }
        }

        public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
            ByteBuffer b;
            if (this.cached != null) {
                super.onDataAvailable(emitter, this.cached);
                if (this.cached.remaining() <= 0) {
                    this.cached = null;
                } else {
                    return;
                }
            }
            ByteBufferList copy = new ByteBufferList();
            try {
                if (this.editor != null) {
                    OutputStream outputStream = this.editor.newOutputStream(1);
                    if (outputStream != null) {
                        while (!bb.isEmpty()) {
                            b = bb.remove();
                            ByteBufferList.writeOutputStream(outputStream, b);
                            copy.add(b);
                        }
                    } else {
                        abort();
                    }
                }
                bb.get(copy);
                copy.get(bb);
            } catch (Exception e) {
                abort();
                bb.get(copy);
                copy.get(bb);
            } catch (Throwable th) {
                bb.get(copy);
                copy.get(bb);
            }
            super.onDataAvailable(emitter, bb);
            if (this.editor != null && bb.remaining() > 0) {
                this.cached = new ByteBufferList();
                bb.get(this.cached);
            }
        }

        public void close() {
            abort();
            super.close();
        }

        public void abort() {
            if (this.editor != null) {
                this.editor.abort();
                this.editor = null;
            }
        }

        public void commit() {
            if (this.editor != null) {
                this.editor.commit();
                this.editor = null;
            }
        }
    }

    public static class CacheData {
        ResponseHeaders cachedResponseHeaders;
        EntryCacheResponse candidate;
        long contentLength;
        FileInputStream[] snapshot;
    }

    private static class CachedBodyEmitter extends FilteredDataEmitter {
        static final /* synthetic */ boolean $assertionsDisabled = (!ResponseCacheMiddleware.class.desiredAssertionStatus());
        private Allocator allocator = new Allocator();
        boolean allowEnd;
        EntryCacheResponse cacheResponse;
        private boolean paused;
        ByteBufferList pending = new ByteBufferList();
        Runnable spewRunnable = new Runnable() {
            public void run() {
                CachedBodyEmitter.this.spewInternal();
            }
        };

        public CachedBodyEmitter(EntryCacheResponse cacheResponse, long contentLength) {
            this.cacheResponse = cacheResponse;
            this.allocator.setCurrentAlloc((int) contentLength);
        }

        void spewInternal() {
            if (this.pending.remaining() > 0) {
                Util.emitAllData(this, this.pending);
                if (this.pending.remaining() > 0) {
                    return;
                }
            }
            try {
                ByteBuffer buffer = this.allocator.allocate();
                if ($assertionsDisabled || buffer.position() == 0) {
                    int read = this.cacheResponse.getBody().read(buffer.array(), buffer.arrayOffset(), buffer.capacity());
                    if (read == -1) {
                        ByteBufferList.reclaim(buffer);
                        this.allowEnd = true;
                        report(null);
                        return;
                    }
                    this.allocator.track((long) read);
                    buffer.limit(read);
                    this.pending.add(buffer);
                    Util.emitAllData(this, this.pending);
                    if (this.pending.remaining() <= 0) {
                        getServer().postDelayed(this.spewRunnable, 10);
                        return;
                    }
                    return;
                }
                throw new AssertionError();
            } catch (IOException e) {
                this.allowEnd = true;
                report(e);
            }
        }

        void spew() {
            getServer().post(this.spewRunnable);
        }

        public void resume() {
            this.paused = false;
            spew();
        }

        public boolean isPaused() {
            return this.paused;
        }

        protected void report(Exception e) {
            if (this.allowEnd) {
                StreamUtility.closeQuietly(this.cacheResponse.getBody());
                super.report(e);
            }
        }
    }

    private class CachedSocket extends CachedBodyEmitter implements AsyncSocket {
        boolean closed;
        CompletedCallback closedCallback;
        boolean open;

        public CachedSocket(EntryCacheResponse cacheResponse, long contentLength) {
            super(cacheResponse, contentLength);
            this.allowEnd = true;
        }

        public void end() {
        }

        protected void report(Exception e) {
            super.report(e);
            if (!this.closed) {
                this.closed = true;
                if (this.closedCallback != null) {
                    this.closedCallback.onCompleted(e);
                }
            }
        }

        public void write(ByteBuffer bb) {
            bb.limit(bb.position());
        }

        public void write(ByteBufferList bb) {
            bb.recycle();
        }

        public WritableCallback getWriteableCallback() {
            return null;
        }

        public void setWriteableCallback(WritableCallback handler) {
        }

        public boolean isOpen() {
            return this.open;
        }

        public void close() {
            this.open = false;
        }

        public void setClosedCallback(CompletedCallback handler) {
            this.closedCallback = handler;
        }

        public AsyncServer getServer() {
            return ResponseCacheMiddleware.this.server;
        }
    }

    private class CachedSSLSocket extends CachedSocket implements AsyncSSLSocket {
        public CachedSSLSocket(EntryCacheResponse cacheResponse, long contentLength) {
            super(cacheResponse, contentLength);
        }
    }

    private static final class Entry {
        private final String cipherSuite;
        private final Certificate[] localCertificates;
        private final Certificate[] peerCertificates;
        private final String requestMethod;
        private final RawHeaders responseHeaders;
        private final String uri;
        private final RawHeaders varyHeaders;

        public Entry(InputStream in) throws IOException {
            Throwable th;
            StrictLineReader reader = null;
            try {
                StrictLineReader reader2 = new StrictLineReader(in, Charsets.US_ASCII);
                try {
                    int i;
                    this.uri = reader2.readLine();
                    this.requestMethod = reader2.readLine();
                    this.varyHeaders = new RawHeaders();
                    int varyRequestHeaderLineCount = reader2.readInt();
                    for (i = 0; i < varyRequestHeaderLineCount; i++) {
                        this.varyHeaders.addLine(reader2.readLine());
                    }
                    this.responseHeaders = new RawHeaders();
                    this.responseHeaders.setStatusLine(reader2.readLine());
                    int responseHeaderLineCount = reader2.readInt();
                    for (i = 0; i < responseHeaderLineCount; i++) {
                        this.responseHeaders.addLine(reader2.readLine());
                    }
                    this.cipherSuite = null;
                    this.peerCertificates = null;
                    this.localCertificates = null;
                    StreamUtility.closeQuietly(reader2, in);
                } catch (Throwable th2) {
                    th = th2;
                    reader = reader2;
                    StreamUtility.closeQuietly(reader, in);
                    throw th;
                }
            } catch (Throwable th3) {
                th = th3;
                StreamUtility.closeQuietly(reader, in);
                throw th;
            }
        }

        public Entry(Uri uri, RawHeaders varyHeaders, AsyncHttpRequest request, ResponseHeaders responseHeaders) {
            this.uri = uri.toString();
            this.varyHeaders = varyHeaders;
            this.requestMethod = request.getMethod();
            this.responseHeaders = responseHeaders.getHeaders();
            this.cipherSuite = null;
            this.peerCertificates = null;
            this.localCertificates = null;
        }

        public void writeTo(EntryEditor editor) throws IOException {
            int i;
            Writer writer = new BufferedWriter(new OutputStreamWriter(editor.newOutputStream(0), Charsets.UTF_8));
            writer.write(this.uri + '\n');
            writer.write(this.requestMethod + '\n');
            writer.write(Integer.toString(this.varyHeaders.length()) + '\n');
            for (i = 0; i < this.varyHeaders.length(); i++) {
                writer.write(this.varyHeaders.getFieldName(i) + ": " + this.varyHeaders.getValue(i) + '\n');
            }
            writer.write(this.responseHeaders.getStatusLine() + '\n');
            writer.write(Integer.toString(this.responseHeaders.length()) + '\n');
            for (i = 0; i < this.responseHeaders.length(); i++) {
                writer.write(this.responseHeaders.getFieldName(i) + ": " + this.responseHeaders.getValue(i) + '\n');
            }
            if (isHttps()) {
                writer.write(10);
                writer.write(this.cipherSuite + '\n');
                writeCertArray(writer, this.peerCertificates);
                writeCertArray(writer, this.localCertificates);
            }
            writer.close();
        }

        private boolean isHttps() {
            return this.uri.startsWith("https://");
        }

        private void writeCertArray(Writer writer, Certificate[] certificates) throws IOException {
            if (certificates == null) {
                writer.write("-1\n");
                return;
            }
            try {
                writer.write(Integer.toString(certificates.length) + '\n');
                for (Certificate certificate : certificates) {
                    writer.write(Base64.encodeToString(certificate.getEncoded(), 0) + '\n');
                }
            } catch (CertificateEncodingException e) {
                throw new IOException(e.getMessage());
            }
        }

        public boolean matches(Uri uri, String requestMethod, Map<String, List<String>> requestHeaders) {
            return this.uri.equals(uri.toString()) && this.requestMethod.equals(requestMethod) && new ResponseHeaders(uri, this.responseHeaders).varyMatches(this.varyHeaders.toMultimap(), requestHeaders);
        }
    }

    static class EntryCacheResponse extends CacheResponse {
        private final Entry entry;
        private final FileInputStream snapshot;

        public EntryCacheResponse(Entry entry, FileInputStream snapshot) {
            this.entry = entry;
            this.snapshot = snapshot;
        }

        public Map<String, List<String>> getHeaders() {
            return this.entry.responseHeaders.toMultimap();
        }

        public FileInputStream getBody() {
            return this.snapshot;
        }
    }

    class EntryEditor {
        boolean done;
        String key;
        FileOutputStream[] outs = new FileOutputStream[2];
        File[] temps;

        public EntryEditor(String key) {
            this.key = key;
            this.temps = ResponseCacheMiddleware.this.cache.getTempFiles(2);
        }

        void commit() {
            StreamUtility.closeQuietly(this.outs);
            if (!this.done) {
                ResponseCacheMiddleware.this.cache.commitTempFiles(this.key, this.temps);
                ResponseCacheMiddleware.this.writeSuccessCount = ResponseCacheMiddleware.this.writeSuccessCount + 1;
                this.done = true;
            }
        }

        FileOutputStream newOutputStream(int index) throws IOException {
            if (this.outs[index] == null) {
                this.outs[index] = new FileOutputStream(this.temps[index]);
            }
            return this.outs[index];
        }

        void abort() {
            StreamUtility.closeQuietly(this.outs);
            FileCache.removeFiles(this.temps);
            if (!this.done) {
                ResponseCacheMiddleware.this.writeAbortCount = ResponseCacheMiddleware.this.writeAbortCount + 1;
                this.done = true;
            }
        }
    }

    private ResponseCacheMiddleware() {
    }

    public static ResponseCacheMiddleware addCache(AsyncHttpClient client, File cacheDir, long size) throws IOException {
        Iterator i$ = client.getMiddleware().iterator();
        while (i$.hasNext()) {
            if (((AsyncHttpClientMiddleware) i$.next()) instanceof ResponseCacheMiddleware) {
                throw new IOException("Response cache already added to http client");
            }
        }
        ResponseCacheMiddleware ret = new ResponseCacheMiddleware();
        ret.server = client.getServer();
        ret.cache = new FileCache(cacheDir, size, false);
        client.insertMiddleware(ret);
        return ret;
    }

    public FileCache getFileCache() {
        return this.cache;
    }

    public Cancellable getSocket(GetSocketData data) {
        if (this.cache == null || !this.caching || data.request.getHeaders().isNoCache()) {
            this.networkCount++;
            return null;
        }
        try {
            FileInputStream[] snapshot = this.cache.get(FileCache.toKeyString(data.request.getUri()), 2);
            if (snapshot == null) {
                this.networkCount++;
                return null;
            }
            long contentLength = (long) snapshot[1].available();
            Entry entry = new Entry(snapshot[0]);
            if (entry.matches(data.request.getUri(), data.request.getMethod(), data.request.getHeaders().getHeaders().toMultimap())) {
                EntryCacheResponse candidate = new EntryCacheResponse(entry, snapshot[1]);
                try {
                    Map<String, List<String>> responseHeadersMap = candidate.getHeaders();
                    FileInputStream cachedResponseBody = candidate.getBody();
                    if (responseHeadersMap == null || cachedResponseBody == null) {
                        this.networkCount++;
                        StreamUtility.closeQuietly(snapshot);
                        return null;
                    }
                    RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(responseHeadersMap);
                    ResponseHeaders cachedResponseHeaders = new ResponseHeaders(data.request.getUri(), rawResponseHeaders);
                    rawResponseHeaders.set("Content-Length", String.valueOf(contentLength));
                    rawResponseHeaders.removeAll("Content-Encoding");
                    rawResponseHeaders.removeAll("Transfer-Encoding");
                    cachedResponseHeaders.setLocalTimestamps(System.currentTimeMillis(), System.currentTimeMillis());
                    ResponseSource responseSource = cachedResponseHeaders.chooseResponseSource(System.currentTimeMillis(), data.request.getHeaders());
                    if (responseSource == ResponseSource.CACHE) {
                        data.request.logi("Response retrieved from cache");
                        CachedSocket cachedSSLSocket;
                        if (entry.isHttps()) {
                            cachedSSLSocket = new CachedSSLSocket(candidate, contentLength);
                        } else {
                            cachedSSLSocket = new CachedSocket(candidate, contentLength);
                        }
                        socket.pending.add(ByteBuffer.wrap(rawResponseHeaders.toHeaderString().getBytes()));
                        final GetSocketData getSocketData = data;
                        final CachedSocket cachedSocket = socket;
                        this.server.post(new Runnable() {
                            public void run() {
                                getSocketData.connectCallback.onConnectCompleted(null, cachedSocket);
                                cachedSocket.spewInternal();
                            }
                        });
                        this.cacheHitCount++;
                        return new SimpleCancellable();
                    } else if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
                        data.request.logi("Response may be served from conditional cache");
                        CacheData cacheData = new CacheData();
                        cacheData.snapshot = snapshot;
                        cacheData.contentLength = contentLength;
                        cacheData.cachedResponseHeaders = cachedResponseHeaders;
                        cacheData.candidate = candidate;
                        data.state.put("cache-data", cacheData);
                        return null;
                    } else {
                        data.request.logd("Response can not be served from cache");
                        this.networkCount++;
                        StreamUtility.closeQuietly(snapshot);
                        return null;
                    }
                } catch (Exception e) {
                    this.networkCount++;
                    StreamUtility.closeQuietly(snapshot);
                    return null;
                }
            }
            this.networkCount++;
            StreamUtility.closeQuietly(snapshot);
            return null;
        } catch (IOException e2) {
            this.networkCount++;
            StreamUtility.closeQuietly(null);
            return null;
        }
    }

    public void onBodyDecoder(OnBodyData data) {
        if (((CachedSocket) Util.getWrappedSocket(data.socket, CachedSocket.class)) != null) {
            data.headers.getHeaders().set("X-Served-From", "cache");
            return;
        }
        CacheData cacheData = (CacheData) data.state.get("cache-data");
        if (cacheData != null) {
            if (cacheData.cachedResponseHeaders.validate(data.headers)) {
                data.request.logi("Serving response from conditional cache");
                data.headers.getHeaders().removeAll("Content-Length");
                data.headers = cacheData.cachedResponseHeaders.combine(data.headers);
                data.headers.getHeaders().setStatusLine(cacheData.cachedResponseHeaders.getHeaders().getStatusLine());
                data.headers.getHeaders().set("X-Served-From", "conditional-cache");
                this.conditionalCacheHitCount++;
                CachedBodyEmitter bodySpewer = new CachedBodyEmitter(cacheData.candidate, cacheData.contentLength);
                bodySpewer.setDataEmitter(data.bodyEmitter);
                data.bodyEmitter = bodySpewer;
                bodySpewer.spew();
                return;
            }
            data.state.remove("cache-data");
            StreamUtility.closeQuietly(cacheData.snapshot);
        }
        if (!this.caching) {
            return;
        }
        if (data.headers.isCacheable(data.request.getHeaders()) && data.request.getMethod().equals("GET")) {
            String key = FileCache.toKeyString(data.request.getUri());
            Entry entry = new Entry(data.request.getUri(), data.request.getHeaders().getHeaders().getAll(data.headers.getVaryFields()), data.request, data.headers);
            BodyCacher cacher = new BodyCacher();
            EntryEditor editor = new EntryEditor(key);
            try {
                entry.writeTo(editor);
                editor.newOutputStream(1);
                cacher.editor = editor;
                cacher.setDataEmitter(data.bodyEmitter);
                data.bodyEmitter = cacher;
                data.state.put("body-cacher", cacher);
                data.request.logd("Caching response");
                this.cacheStoreCount++;
                return;
            } catch (Exception e) {
                editor.abort();
                this.networkCount++;
                return;
            }
        }
        this.networkCount++;
        data.request.logd("Response is not cacheable");
    }

    public void onRequestComplete(OnRequestCompleteData data) {
        CacheData cacheData = (CacheData) data.state.get("cache-data");
        if (!(cacheData == null || cacheData.snapshot == null)) {
            StreamUtility.closeQuietly(cacheData.snapshot);
        }
        if (((CachedSocket) Util.getWrappedSocket(data.socket, CachedSocket.class)) != null) {
            StreamUtility.closeQuietly(((CachedSocket) Util.getWrappedSocket(data.socket, CachedSocket.class)).cacheResponse.getBody());
        }
        BodyCacher cacher = (BodyCacher) data.state.get("body-cacher");
        if (cacher == null) {
            return;
        }
        if (data.exception != null) {
            cacher.abort();
        } else {
            cacher.commit();
        }
    }
}
